Java中存在着四种引用类型分别为:强引用(Strong Reference)弱引用(Weak Reference)软引用(Soft Reference)虚引用(Phantom Reference),每个引用对象内部都有一个对应的引用对象Referent和一个ReferenceQueue. 这四个引用类类型的父类是ReferenceReference 是一个抽象类,它定义了所有引用对象共同的操作方法。因为引用对象和垃圾回收操作关系密切,因此一般不需要直接实现该类。

⭐️记忆技巧:强引用(程序运行过程不会被回收) > 软引用(内存不足时被回收) > 弱引用 (垃圾回收机制执行就被回收)> 虚引用(用于跟踪对象被垃圾回收的状态)

1. 强引用(Strong Reference)

强引用通常是指将一个对象赋值给一个引用变量,这个引用变量就称为是强引用(Strong Reference),此类引用不会被GC机制回收,即使不使用该引用变量,也不会被回收,直至程序出现了OOM(Out Of Mermory)异常,所以通常强引用也是造成内存泄漏的主要原因之一,例子如下所示:

1
2
3
4
public void test() {
// Strong Reference
Test test = new Test();
}

2. 弱引用(Weak Reference)

弱引用在垃圾回收执行时,不管JVM内存空间是否够用,该引用对象都会被回收。其引用需要使用WeakReference类来实现,它比软引用的生存期相对更短。

其中,WeakReference中有两个构造参数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class WeakReference<T> extends Reference<T> {
/**
* Creates a new weak reference that refers to the given object. The new
* reference is not registered with any queue.
*
* @param referent object the new weak reference will refer to
*/
public WeakReference(T referent) {
super(referent);
}

/**
* Creates a new weak reference that refers to the given object and is
* registered with the given queue.
*
* @param referent object the new weak reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}

public WeakReference(T referent, ReferenceQueue<? super T> q)该构造函数中的第一个参数T referent就是弱引用对象,第二个参数ReferenceQueue<? super T> q则是用来存储封装的待回收的Reference对象的。

具体例子如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*  
运行结果如下所示:

当前标记为弱引用的变量:[B@29453f44
当前弱引用对象:java.lang.ref.WeakReference@5cad8086
回收前的弱引用变量:[B@29453f44
回收后的弱引用变量:null
引用队列中被随之回收的WeakReference对象java.lang.ref.WeakReference@5cad8086
*/

public class RefTest {
/**
* 1KB = 1024B
*/
private static final int K = 1024;
/**
* 1MB = 1024KB
*/
private static final int M = 1024 * K;

/**
* JVM Options: -Xms20M -Xmm20M -Xmn5M
* 初始内存20MB,总内存20MB,年轻代5MB
*/
public static void main(String[] args) {
// 占用10MB的总内存
byte[] ref_1 = new byte[10 * M];
// 占用3MB的总内存
byte[] ref_2 = new byte[3 * M];
ReferenceQueue referenceQueue = new ReferenceQueue();
WeakReference weakReference = new WeakReference(ref_2, referenceQueue);
System.out.println("当前标记为弱引用的变量:"+weakReference.get());
System.out.println("当前弱引用对象:"+weakReference);
// 舍弃Strong Reference,给JVM GC回收该引用的机会
ref_2 = null;
System.out.println("回收前的弱引用变量:"+weakReference.get());
// 产生内部GC回收ref_2内存空间,随后创建ref_3
byte[] ref_3 = new byte[4 * M];
// 获取弱引用中的ref_2对象情况
System.out.println("回收后的弱引用变量:"+weakReference.get());
System.out.println("引用队列中被随之回收的WeakReference对象"+referenceQueue.poll());
}
}

创建ref_1对象:

其中年轻代大小为5MB,随后创建的ref_1占用大小为10MB,年轻代总大小(5MB)不够存储,根据Java堆存放机制,将ref_1对象置入剩余的15MB空间中。

结果:

年轻代剩余大小:5MB

堆其他空间(含老年代)剩余大小:5MB

创建ref_2对象:

创建的ref_2对象大小为3MB,存储入年轻代Eden区中,随后将其设置为弱引用(Weak Reference)

结果:

年轻代剩余大小:2MB

堆其他空间(含老年代)剩余大小:5MB

创建ref_3对象:

待创建的ref_3对象大小为4MB,而此时,强引用对象ref_2已解除,JVM可在剩余内存不足时进行GC回收,而因为年轻代剩余大小仅为2MB,则对其进行Minor GC回收,清理并释放弱引用(Weak Reference)的ref_2内存空间,随后放入大小为5MB的年轻代中。

此时的ref_3对象大小若为5MB,则与年轻代和堆其他空间大小形成一致,无法放入年轻代中,也无法放入堆其他空间中(因为放进任何一个都满了),随之将报OutOfMemoryError异常

结果:

年轻代剩余大小:1MB

堆其他空间(含老年代)剩余大小:5MB

3. 软引用(Soft Reference)

软引用与弱引用十分相似,但总有区别。弱引用(Weak Reference)在遇到系统进行垃圾回收时,不管其剩余空间是否足够用,都将被回收,而软引用(Soft Reference)当遇到系统进行垃圾回收时,剩余内存空间足够时,不会被回收,只有在内存不足时,才会被回收。也就是说软引用(Soft Reference)在当没有强引用(Strong Reference)指向它时,会在内存中停留一段时间。测试例子与上大部分相同,读者可自行修改后尝试。

1
2
3
// Soft Reference Usage
SoftReference<Bean> bean = new SoftReference<Bean>(new Bean("name", 10));
System.out.println(bean.get());// "name:10”

软引用有以下特征:

  • 软引用使用 get()方法取得对象的强引用从而访问目标对象。
  • 软引用所指向的对象按照 JVM 的使用情况(Heap 内存是否临近阈值)来决定是否回收。
  • 软引用可以避免 Heap 内存不足所导致的异常。

垃圾回收器决定对其回收时,会先清空它的 SoftReference,也就是说 SoftReferenceget()方法将会返回 null,然后再调用对象的finalize()方法,并在下一轮 GC 中对其真正进行回收。

4. 虚引用(Phantom Reference)

虚引用(Phantom Reference)是所有”弱引用”中最弱的引用类型。不同于软引用和弱引用,虚引用无法通过 get() 方法来取得目标对象的强引用从而使用目标对象,观察源码可以发现 get() 被重写为永远返回 null。

1
2
3
4
5
6
7
8
9
10
/**
* Returns this reference object's referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* <code>null</code>.
*
* @return <code>null</code>
*/
public T get() {
return null;
}

虚引用需要使用PhantomReference类来实现,并且它不能单独使用,必须和引用队列ReferenceQueue一起使用,使用例子如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RefTest {
public static void main(String[] args) {
ReferenceQueue<String> refQueue = new ReferenceQueue<String>();
PhantomReference<String> referent = new PhantomReference<String>(
new String("T"), refQueue);
// null
System.out.println(referent.get());

System.gc();
System.runFinalization();

// true
System.out.println(refQueue.poll() == referent);
}
}

对于引用回收方面,虚引用(Phantom Reference)类似强引用(Strong Reference)不会根据内存情况自动对对象空间进行回收处理,此时则需要我们手动对其处理以防Heap空间不足异常。

虚引用有以下特征:

  • 虚引用永远无法使用get()方法取得对象的强引用从而访问目标对象。
  • 虚引用所指向的对象在被系统内存回收前,虚引用自身会被放入 ReferenceQueue对象中从而跟踪对象垃圾回收。
  • 虚引用不会根据内存情况自动回收目标对象

另外值得注意的是,其实 SoftReferenceWeakReference 以及 PhantomReference 的构造函数都可以接收一个 ReferenceQueue对象。当 SoftReference以及WeakReference被清空的同时,也就是 Java 垃圾回收器准备对它们所指向的对象进行回收时,调用对象的 finalize() 方法之前,它们自身会被加入到这个 ReferenceQueue 对象 中,此时可以通过 ReferenceQueuepoll() 方法取到它们。而 PhantomReference 只有当 Java 垃圾回收器对其所指向的对象真正进行回收时,会将其加入到这个 ReferenceQueue 对象 中,这样就可以追综对象的销毁情况。


5. 总结

引用类型 取得目标对象方式 垃圾回收条件 是否可能内存泄漏
强引用 直接调用 不回收 可能
软引用 通过 get() 方法 视内存情况回收 不可能
弱引用 通过 get() 方法 永远回收 不可能
虚引用 无法取得 不回收 可能

注意:如果想使用这些相对强引用来说较弱的引用来进行对象操作的时候,就必须保证没有强引用指向被操作对象。否则将会被视为强引用指向,不会具有任何的弱引用的特性(可参考本文中弱引用(Weak Reference)示例代码中ref_2的置null操作)